#include "../config.h"

#include <gtkmm.h>
#include <boost/version.hpp>

#include "application.h"
#include "format.h"
#include "misc.h"
#include "environment.h"

#ifdef USE_MIGEMO
# include <migemo.h>
#endif

namespace gpyp {

//========================
// AttributeColor
Color AttributeColor::to_value(const Glib::ustring &str) {
  return str == "none" ? Color() : Color(str);
}
Glib::ustring AttributeColor::to_string(const Color &value) {
  if (value) {
    return format("#%04hx%04hx%04hx",
                  value.get_red(),
                  value.get_green(),
                  value.get_blue());
  } else {
    return "none";
  }
}

//========================
// AttributeHost
void AttributeHost::on_changed() {
  update();
  conf::AttributeStr::on_changed();
}

void AttributeHost::update() {
  const Glib::ustring &v = get_value();
  Glib::ustring::size_type pos = v.rfind(':');
  if (pos == Glib::ustring::npos) {
    host_  = v;
    port_  = def_port_;
    valid_ = true;
  } else {
    host_.assign(v, 0, pos);
    Glib::ustring port_s(v.substr(pos + 1));
    int port = toint(port_s, -1);
    port_  = port < 0 ? def_port_ : port;
    valid_ = !port_s.empty() && port >= 0;
  }
  if (valid_) {
    if (host_.bytes() < 256) {
      size_t labelsize = 0;
      for(const char *c = host_.c_str(); *c; ++c) {
        switch(*c) {
        case '.':
          if (labelsize < 1 || labelsize > 63) {
            valid_ = false;
          }
          labelsize = 0;
          break;
        case '-':
          ++labelsize;
          break;
        default:
          if ((*c >= 'A' && *c <= 'Z') ||
              (*c >= 'a' && *c <= 'z') ||
              (*c >= '0' && *c <= '9')) {
            ++labelsize;
          } else if (valid_) {
            valid_ = boost::logic::indeterminate;
          }
          break;
        }
        if (!valid_) {
          break;
        }
      }
      if (labelsize > 63) {
        valid_ = false;
      }
    } else {
      valid_ = false;
    }
  }
}

AttributeHost::AttributeHost(const Glib::ustring &name, const Glib::ustring &def_host, uint def_port) :
  conf::AttributeStr(name, create_str(def_host, def_port)),
  def_host_(def_host),
  def_port_(def_port)
{
  update();
}

//========================
// AttributeFormat
void AttributeFormat::on_changed() {
  format_terms_->parse(get_value());
  conf::AttributeStr::on_changed();
}

int AttributeFormat::compare(RefChannel ch1, RefChannel ch2) {
  return format_terms_->compare(ch1, ch2);
}

bool AttributeFormat::format(Glib::ustring &str, RefChannel ch, bool html) {
  return format_terms_->format(str, ch, html);
}

AttributeFormat::AttributeFormat(const Glib::ustring &name,
                                 const Glib::ustring &defval,
                                 Application &app) :
  conf::AttributeStr(name, defval),
  format_terms_     (new FormatTermList(app.conf(),
                                        app.ypcollection(),
                                        get_value()))
{
}

//========================
// RefAttributeFormat
RefAttributeFormat::RefAttributeFormat() {
}
RefAttributeFormat::RefAttributeFormat(AttributeFormat *ptr) :
  conf::RefAttribute<AttributeFormat>(ptr)
{
}
RefAttributeFormat::RefAttributeFormat(const Glib::ustring &name,
                                       const Glib::ustring &defval,
                                       Application &app) :
  conf::RefAttribute<AttributeFormat>(new AttributeFormat(name, defval, app))
{
}
RefAttributeFormat::RefAttributeFormat(const RefAttributeFormat &attr) :
  conf::RefAttribute<AttributeFormat>(attr)
{
}

//========================
// AttributeUA
Glib::ustring AttributeUA::_info;

Glib::ustring AttributeUA::get_distribution(const Glib::ustring &suffix) {
  Glib::ustring dist = env::get_distribution();
  if (!dist.empty()) dist += suffix;
  return dist;
}

void AttributeUA::on_changed() {
  update();
  conf::AttributeStr::on_changed();
}

void AttributeUA::update() {
  ua_.clear();
  const char *l = get_value().c_str();
  for(const char *c = l; *c; ++c) {
    switch(*c) {
    case '%':
      if (c[1] == '{') {
        ua_.append(l, c);
        for(l = c + 2; *l && *l != '}'; ++l) ;
        Glib::ustring key(c, l + 1);
        if (key == "%{APP_NAME}") {
          key = PACKAGE_NAME;
        } else if (key == "%{APP_VERSION}") {
          key = PACKAGE_VERSION;
        } else if (key == "%{INFO}") {
          key = _info;
        }
        ua_.append(key);
        c = l++;
      }
      break;
    }
  }
  ua_.append(l);
}

const Glib::ustring &AttributeUA::get_ua() {
  return ua_;
}

AttributeUA::AttributeUA(const Glib::ustring &name,
                         const Glib::ustring &defval) :
  conf::AttributeStr(name, defval)
{
  if (_info.empty()) {
    std::stringstream ss;
    ss << env::get_os() << "; "
       << get_distribution("; ")
       << "gtkmm"  << env::get_gtkmm_version()  << "; "
       << "glibmm" << env::get_glibmm_version() << "; "
       << "boost"BOOST_LIB_VERSION;

#if (REGEX_MODULE == REGEX_MODULE_ONIGURUMA)
    ss << "; Oniguruma" << onig_version();
#endif
#ifdef USE_MIGEMO
    ss << "; C/Migemo"MIGEMO_VERSION;
#endif

    _info = ss.str();
  }

  update();
}

//========================
// AttributeCLModel
AttributeCLModel::NameTable AttributeCLModel::_name_table[] = {
  {CLMODEL_DEFAULT, "default"},
  {CLMODEL_PLAYED,  "played"},
  {CLMODEL_ENDED,   "ended"},
  {CLMODEL_INVALID, NULL},
};

//========================
// AttributePositionType
AttributePositionType::NameTable AttributePositionType::_name_table[] = {
  {Gtk::POS_LEFT,        "left"},
  {Gtk::POS_RIGHT,       "right"},
  {Gtk::POS_TOP,         "top"},
  {Gtk::POS_BOTTOM,      "bottom"},
  {(Gtk::PositionType)0, NULL}
};

//========================
// AttributePanelPos
AttributePanelPos::NameTable AttributePanelPos::_name_table[] = {
  {PANEL_POSITION_NONE,    "none"},
  {PANEL_POSITION_LEFT,    "left"},
  {PANEL_POSITION_RIGHT,   "right"},
  {PANEL_POSITION_TOP,     "top"},
  {PANEL_POSITION_BOTTOM,  "bottom"},
  {PANEL_POSITION_INVALID, NULL},
};

//========================
// AttributeAccel
void AttributeAccel::on_changed() {
  const Glib::ustring &value = get_value();
  if (value.empty()) {
    key_  = 0;
    mods_ = (Gdk::ModifierType)0;
  } else {
    Gtk::AccelGroup::parse(value, key_, mods_);
    mods_ &= Gtk::AccelGroup::get_default_mod_mask();
  }
}

void AttributeAccel::set(guint key, Gdk::ModifierType mods) {
  set_value(AttributeAccel::name(key, mods));
}

AttributeAccel::AttributeAccel(const Glib::ustring &name,
                               guint                key,
                               Gdk::ModifierType    mods) :
  conf::AttributeStr(name, AttributeAccel::name(key, mods)),
  key_              (key),
  mods_             (mods)
{
  set(key, mods);
}

//========================
// GObjectPropertyManager
template<>
void GObjectPropertyManager<conf::RefAttributeStr>::attr_on_changed() {
  g_object_set(obj_,
               name_.c_str(), (*attr_).empty() ? NULL : (*attr_).c_str(),
               NULL);
}

//========================
// Regex
// http://library.gnome.org/devel/glibmm/stable/classGlib_1_1Regex.html
// http://www.boost.org/doc/libs/1_34_1/libs/regex/doc/index.html
// http://www.boost.org/doc/libs/1_34_1/libs/regex/doc/icu_strings.html
// http://www.boost.org/doc/libs/1_34_1/libs/regex/doc/syntax_perl.html
// http://www.geocities.jp/kosako3/oniguruma/index_ja.html
#if (REGEX_MODULE == REGEX_MODULE_GLIB)
const Glib::RegexCompileFlags Regex::DEFAULT_OPTS = Glib::REGEX_OPTIMIZE;
#endif

#if (REGEX_MODULE == REGEX_MODULE_BOOST)
boost::u32regex Regex::boost_create_regex(const Glib::ustring &str,
                                          bool                 caseless) {
  using namespace boost;
  regex_constants::syntax_option_type opts = regex_constants::perl;
  if (caseless) opts |= regex_constants::icase;

  return boost::make_u32regex(str.c_str(), opts);
}
#endif

Glib::ustring Regex::escape(const Glib::ustring &str) {
#if (REGEX_MODULE == REGEX_MODULE_GLIB)
  return Glib::Regex::escape_string(str);
#else
  static const char *esc_chars = ".[]{}()\\*+?|^$";
  Glib::ustring res;
  res.reserve(str.bytes() << 1);
  const char *s = str.c_str();
  for(const char *p = s; *p; ++p) {
    if (strchr(esc_chars, *p)) {
      res.append(s, p);
      res += '\\';
      s = p;
    }
  }
  res.append(s);
  return res;
#endif
}

bool Regex::match(const Glib::ustring &str) {
#if (REGEX_MODULE == REGEX_MODULE_GLIB)
  return re_->match(str);
#elif (REGEX_MODULE == REGEX_MODULE_BOOST)
  return boost::u32regex_search(str.c_str(), re_);
#elif (REGEX_MODULE == REGEX_MODULE_ONIGURUMA)
  const char *str_s = str.c_str(), *str_e = str_s + str.bytes();
  return onig_search(re_,
                     (const UChar *)str_s, (const UChar *)str_e,
                     (const UChar *)str_s, (const UChar *)str_e,
                     NULL, ONIG_OPTION_NONE) >= 0;
#endif
}
bool Regex::search(const Glib::ustring &str,
                   long                &start,
                   long                &length,
                   long                 start_pos) {
#if (REGEX_MODULE == REGEX_MODULE_GLIB)
  GMatchInfo *match_info = NULL;
  const char *str_c = g_utf8_offset_to_pointer(str.c_str(), start_pos);
  bool res = g_regex_match(re_->gobj(), str_c,
                           (GRegexMatchFlags)0, &match_info) != FALSE;
  gint s, e;
  start = length = 0;
  if (match_info) {
    if (g_match_info_fetch_pos(match_info, 0, &s, &e)) {
      start  = start_pos + g_utf8_strlen(str_c, s);
      length = g_utf8_strlen(str_c + s, e - s);
    }
    g_match_info_free(match_info);
  }
  return res;
#elif (REGEX_MODULE == REGEX_MODULE_BOOST)
  boost::cmatch what;
  const char *str_c = g_utf8_offset_to_pointer(str.c_str(), start_pos);
  bool res = boost::u32regex_search(str_c, what, re_);
  if (what.size() > 0) {
    boost::cmatch::difference_type pos =
      what.position(static_cast<boost::cmatch::size_type>(0));
    start  = start_pos + g_utf8_strlen(str_c, pos);
    length = g_utf8_strlen(str_c + pos, what.length(0));
  } else {
    start = length = 0;
  }
  return res;
#elif (REGEX_MODULE == REGEX_MODULE_ONIGURUMA)
  const char *str_s = g_utf8_offset_to_pointer(str.c_str(), start_pos);
  const char *str_e = str.c_str() + str.bytes();
  OnigRegion *region = onig_region_new();
  bool res = onig_search(re_,
                         (const UChar *)str_s, (const UChar *)str_e,
                         (const UChar *)str_s, (const UChar *)str_e,
                         region, ONIG_OPTION_NONE) >= 0;
  if (res) {
    start  = start_pos + g_utf8_strlen(str_s, region->beg[0]);
    length = g_utf8_strlen(str_s + region->beg[0],
                           region->end[0] - region->beg[0]);
  } else {
    start = length = 0;
  }
  onig_region_free(region, 1);
  return res;
#endif
}
bool Regex::search(const Glib::ustring &str,
                   Glib::ustring       &res) {
  long start = 0, length = 0;
  if (search(str, start, length)) {
    res.assign(str, start, length);
    return true;
  }
  return false;
}
std::map<long, long> Regex::search_all(const Glib::ustring &str) {
  std::map<long, long> res;
  long pos = 0;
  while(1) {
    long start = 0, length = 0;
    if (search(str, start, length, pos) && length) {
      res.insert(std::make_pair(start, length));
      pos = start + length;
    } else {
      break;
    }
  }
  return res;
}

Regex::Regex(const Glib::ustring &str, bool caseless) :
  source_(str),
#if (REGEX_MODULE == REGEX_MODULE_GLIB)
  re_(Glib::Regex::create(str, (caseless
                                ? Glib::REGEX_CASELESS | DEFAULT_OPTS
                                : DEFAULT_OPTS)))
#elif (REGEX_MODULE == REGEX_MODULE_BOOST)
  re_(boost_create_regex(str, caseless))
#elif (REGEX_MODULE == REGEX_MODULE_ONIGURUMA)
  re_(NULL)
#endif
{
#if (REGEX_MODULE == REGEX_MODULE_ONIGURUMA)
  OnigErrorInfo err_info;
  OnigOptionType option = ONIG_OPTION_DEFAULT;
  if (caseless) option |= ONIG_OPTION_IGNORECASE;
  int r = onig_new(&re_,
                   (const UChar *)str.c_str(),
                   (const UChar *)(str.c_str() + str.bytes()),
                   option,
                   ONIG_ENCODING_UTF8,
                   ONIG_SYNTAX_PERL,
                   &err_info);
  if (r != ONIG_NORMAL) {
    char *msg = new char[ONIG_MAX_ERROR_MESSAGE_LEN];
    onig_error_code_to_str((OnigUChar *)msg, r, &err_info);
    Exception ex(msg);
    delete [] msg;
    throw ex;
  }
#endif
}
Regex::~Regex() {
#if (REGEX_MODULE == REGEX_MODULE_ONIGURUMA)
  onig_free(re_);
#endif
}

#ifndef DISABLE_STATUS_ICON
//========================
// StatusIcon
#ifdef GTK_HAVE_STATUS_ICON
void StatusIcon::status_icon_on_activate() {
  signal_activate_();
}
void StatusIcon::status_icon_on_popup_menu(guint button, guint32 activate_time) {
  menu_->popup(button, activate_time);
}
bool StatusIcon::status_icon_on_size_changed(int size) {
  if (!icon_name_.empty()) {
    Glib::RefPtr<Gdk::Pixbuf> icon = IconManager::load_icon(icon_name_,
                                                            size,
                                                            IconManager::ICON_SIZE_48,
                                                            Gtk::ICON_LOOKUP_USE_BUILTIN);
    status_icon_->set(icon);
  }
  return true;
}
void StatusIcon::update_icon() {
  status_icon_on_size_changed(status_icon_->get_size());
}
#endif

void StatusIcon::reset_icon() {
#ifdef GTK_HAVE_STATUS_ICON
  icon_name_ = default_icon_name_;
  update_icon();
#endif
}
void StatusIcon::set_icon(const Glib::ustring &icon_name) {
#ifdef GTK_HAVE_STATUS_ICON
  icon_name_ = icon_name;
  update_icon();
#endif
}
void StatusIcon::set_icon(const Gtk::StockID &stock_id) {
#ifdef GTK_HAVE_STATUS_ICON
  icon_name_.clear();
  status_icon_->set(stock_id);
#endif
}

void StatusIcon::set_tooltip(const Glib::ustring &tooltip_text) {
#ifdef GTK_HAVE_STATUS_ICON
  status_icon_->set_tooltip(tooltip_text);
#endif
}
void StatusIcon::set_visible(bool visible) {
#ifdef GTK_HAVE_STATUS_ICON
  status_icon_->set_visible(visible);
#endif
}

StatusIcon::StatusIcon(Application &app, const Glib::ustring &default_icon_name) :
  conf::Element     ("status_icon"),
  enable_           ("enable", true),
  menu_             (new StatusIconMenu(app))
#ifdef GTK_HAVE_STATUS_ICON
  ,
  status_icon_      (Gtk::StatusIcon::create(default_icon_name)),
  default_icon_name_(default_icon_name),
  icon_name_        (default_icon_name)
#endif
{
  add(enable_);

#ifdef GTK_HAVE_STATUS_ICON
  enable_->attach(status_icon_->gobj(), "visible");

  conn_activate_ = status_icon_->signal_activate()
    .connect(sigc::mem_fun(*this, &StatusIcon::status_icon_on_activate));
  conn_popup_menu_ = status_icon_->signal_popup_menu()
    .connect(sigc::mem_fun(*this, &StatusIcon::status_icon_on_popup_menu));
  conn_size_changed_ = status_icon_->signal_size_changed()
    .connect(sigc::mem_fun(*this, &StatusIcon::status_icon_on_size_changed));
#endif
}
StatusIcon::~StatusIcon() {
  conn_activate_    .disconnect();
  conn_popup_menu_  .disconnect();
  conn_size_changed_.disconnect();
  delete menu_;
}
#endif

}/*gpyp*/
